<?php

namespace paper\http;

use Closure;
use JetBrains\PhpStorm\Pure;
use paper\attribute\WithoutMiddleware;
use paper\constraint\Controller;
use paper\constraint\RouteParser;
use paper\exception\HttpException;
use paper\http\response\ClosureResponse;
use paper\http\response\Response;
use ReflectionClass;
use ReflectionException;

class Router
{

    private RouteParser $parser;
    private MiddlewareManager $middlewareManager;
    private ?RoutingRules $routingRules = null;

    public function __construct(RouteParser $parser, MiddlewareManager $middlewareManager)
    {
        $this->parser = $parser;
        $this->middlewareManager = $middlewareManager;
    }

    /**
     * @param RoutingRules $routingRules
     */
    public function setRoutingRules(RoutingRules $routingRules): void
    {
        $this->routingRules = $routingRules;
    }

    /**
     * @param Request $request
     * @return Response
     * @throws HttpException
     */
    public function dispatch(Request $request): Response
    {
        $callable = $this->parser->parse($request, $this, $this->routingRules);
        if (!$callable) {
            throw new HttpException(404);
        }
        $dispatch = null;
        if (is_array($callable)) {
            $controllerClass = $callable[0];
            $actionName = $callable[1];
            try {
                $reflectionClass = new ReflectionClass($controllerClass);
                $reflectionMethod = $reflectionClass->getMethod($actionName);
            } catch (ReflectionException $exception) {
                throw new HttpException(404, $exception->getMessage());
            }
            if ($reflectionMethod->isPublic()) {
                $dispatch = function () use ($reflectionMethod, $actionName, $request, $reflectionClass) {
                    $controller = $reflectionClass->newInstance();
                    if ($controller instanceof Controller) {
                        $controller->onBefore($request);
                        $return = $reflectionMethod->invoke($controller, $request);
                        $response = $this->return2response($return);
                        $controller->onAfter($response);
                    } else {
                        $return = $reflectionMethod->invoke($controller, $request);
                        $response = $this->return2response($return);
                    }
                    return $response;
                };
            }

        } else if ($callable instanceof Closure) {
            $dispatch = function () use ($request, $callable) {
                $return = $callable($request);
                return $this->return2response($return);
            };
        }
        if ($dispatch != null) {
            return $this->runMiddleware($request, $dispatch);
        }
        throw new HttpException(404);
    }

    private function runMiddleware(Request $request, Closure $then)
    {
        return $this->middlewareManager->then($request, $then);
    }

    #[Pure] private function return2response($return): Response
    {
        if ($return instanceof Response)
            $response = $return;
        else {
            $response = new ClosureResponse(function () use ($return) {
                echo $return;
            });
        }
        return $response;
    }


    public function addMiddleware(string $middleware)
    {
        $this->middlewareManager->register($middleware);
    }

    public function addMiddlewares(array $middleware)
    {
        $this->middlewareManager->import($middleware);
    }

    public function removeMiddlewares(array $middleware)
    {
        $this->middlewareManager->unregister($middleware);
    }
}